package org.example.project.common.middleware.service.impl;

import org.apache.commons.lang3.builder.ToStringBuilder;
import org.example.project.common.middleware.controller.vo.RedisNode;
import org.example.project.common.middleware.service.RedisService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.data.redis.connection.RedisClusterNode;
import org.springframework.data.redis.core.*;
import org.springframework.data.redis.core.script.RedisScript;
import org.springframework.data.redis.core.types.RedisClientInfo;
import org.springframework.stereotype.Service;
import org.springframework.util.Assert;

import java.time.Duration;
import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

import static org.apache.commons.lang3.builder.ToStringStyle.SHORT_PREFIX_STYLE;

/**
 * 封装redisTemplate实现的redis操作
 *
 * @author wenxy
 * @date 2020/9/29
 */
@Service("defaultRedisService")
@ConditionalOnClass(RedisTemplate.class)
public class DefaultRedisServiceImpl implements RedisService<Object, Object> {
    @Autowired
    RedisTemplate redisTemplate;

    @Override
    public void set(Object key, Object value) {
        redisTemplate.opsForValue().set(key, value);
    }

    @Override
    public void set(Object key, Object value, Duration timeout) {
        redisTemplate.opsForValue().set(key, value, timeout);
    }

    @Override
    public Boolean setIfAbsent(Object key, Object value) {
        return redisTemplate.opsForValue().setIfAbsent(key, value);
    }

    @Override
    public Boolean setIfAbsent(Object key, Object value, Duration timeout) {
        return redisTemplate.opsForValue().setIfAbsent(key, value, timeout);
    }

    @Override
    public Boolean setIfPresent(Object key, Object value) {
        return redisTemplate.opsForValue().setIfPresent(key, value);
    }

    @Override
    public Boolean setIfPresent(Object key, Object value, Duration timeout) {
        return redisTemplate.opsForValue().setIfPresent(key, value, timeout);
    }

    @Override
    public void multiSet(Map<?, ?> map) {
        redisTemplate.opsForValue().multiSet(map);
    }

    @Override
    public Boolean multiSetIfAbsent(Map<?, ?> map) {
        return redisTemplate.opsForValue().multiSetIfAbsent(map);
    }

    @Override
    public Object get(Object key) {
        return redisTemplate.opsForValue().get(key);
    }

    @Override
    public List<Object> multiGet(Collection<Object> keys) {
        return redisTemplate.opsForValue().multiGet(keys);
    }

    @Override
    public Long increment(Object key) {
        return redisTemplate.opsForValue().increment(key);
    }

    @Override
    public Long increment(Object key, long delta) {
        return redisTemplate.opsForValue().increment(key, delta);
    }

    @Override
    public Long decrement(Object key) {
        return redisTemplate.opsForValue().decrement(key);
    }

    @Override
    public Long decrement(Object key, long delta) {
        return redisTemplate.opsForValue().decrement(key, delta);
    }

    @Override
    public String get(Object key, long start, long end) {
        return redisTemplate.opsForValue().get(key, start, end);
    }

    @Override
    public void set(Object key, Object value, long offset) {
        redisTemplate.opsForValue().set(key, value, offset);
    }

    @Override
    public Long size(Object key) {
        return redisTemplate.opsForValue().size(key);
    }

    @Override
    public Boolean setBit(Object key, long offset, boolean value) {
        return redisTemplate.opsForValue().setBit(key, offset, value);
    }

    @Override
    public Boolean getBit(Object key, long offset) {
        return redisTemplate.opsForValue().getBit(key, offset);
    }

    @Override
    public List<Object> lGet(Object key, long start, long end) {
        return redisTemplate.opsForList().range(key, start, end);
    }

    @Override
    public void lRemove(Object key, long start, long end) {
        redisTemplate.opsForList().remove(key, start, end);
    }

    @Override
    public Long lSize(Object key) {
        return redisTemplate.opsForList().size(key);
    }

    @Override
    public Long lAddFirst(Object key, Object value) {
        return redisTemplate.opsForList().leftPush(key, value);
    }

    @Override
    public Long lAddFirst(Object key, Collection<Object> values) {
        return redisTemplate.opsForList().leftPushAll(key, values);
    }

    @Override
    public Long lAddFirstIfPresent(Object key, Object value) {
        return redisTemplate.opsForList().leftPush(key, value);
    }

    @Override
    public Long lAdd(Object key, Object value) {
        return redisTemplate.opsForList().rightPush(key, value);
    }

    @Override
    public Long lAdd(Object key, Collection<Object> values) {
        return redisTemplate.opsForList().leftPushAll(key, values);
    }

    @Override
    public Long lAddIfPresent(Object key, Object value) {
        return redisTemplate.opsForList().leftPushIfPresent(key, value);
    }

    @Override
    public void lAdd(Object key, long index, Object value) {
        redisTemplate.opsForList().set(key, index, value);
    }

    @Override
    public Object lGet(Object key, long index) {
        return redisTemplate.opsForList().index(key, index);
    }


    @Override
    public Boolean hasKey(Object key) {
        return redisTemplate.hasKey(key);
    }

    @Override
    public Long delete(Collection<Object> keys) {
        return redisTemplate.delete(keys);
    }

    @Override
    public Long unlink(Collection<Object> keys) {
        return redisTemplate.unlink(keys);
    }

    @Override
    public String type(Object key) {
        return Objects.requireNonNull(redisTemplate.type(key)).toString();
    }

    @Override
    public Boolean expire(Object key, Duration timeout) {
        Assert.notNull(timeout, "Timeout must not be null!");

        if (TimeoutUtils.hasMillis(timeout)) {
            return redisTemplate.expire(key, timeout.toMillis(), TimeUnit.MILLISECONDS);
        }

        return redisTemplate.expire(key, timeout.getSeconds(), TimeUnit.SECONDS);
    }

    @Override
    public Boolean persist(Object key) {
        return redisTemplate.persist(key);
    }

    @Override
    public Long getExpire(Object key, TimeUnit timeUnit) {
        return redisTemplate.getExpire(key, timeUnit);
    }

    @Override
    public List<String> getClientListInfos() {
        List<RedisClientInfo> redisClientInfos = Objects.requireNonNull(redisTemplate.getClientList());
        return redisClientInfos.stream().map(RedisClientInfo::toString).collect(Collectors.toList());
    }

    @Override
    public List<Object> executePipelined(RedisCallback<?> action) {
        return redisTemplate.executePipelined(action);
    }

    @Override
    public <T> T execute(RedisScript<T> script, List<Object> keys, Object... args) {
        return (T) redisTemplate.execute(script, keys, args);
    }

    @Override
    public String ping(RedisNode redisNode) {
        return redisTemplate.opsForCluster().ping(redisNode.toRedisClusterNode());
    }

    @Override
    public void forget(RedisNode redisNode) {
        redisTemplate.opsForCluster().forget(redisNode.toRedisClusterNode());
    }

    @Override
    public Collection<String> getSlaves(RedisNode redisNode) {
        Collection<RedisClusterNode> slaves = redisTemplate.opsForCluster().getSlaves(redisNode.toRedisClusterNode());
        return slaves.stream().map(this::toString).collect(Collectors.toList());
    }

    private String toString(RedisClusterNode redisClusterNode) {
        return ToStringBuilder.reflectionToString(redisClusterNode, SHORT_PREFIX_STYLE);
    }

    @Override
    public void reShard(RedisNode source, int slot, RedisNode target) {
        redisTemplate.opsForCluster().reshard(source.toRedisClusterNode(), slot, target.toRedisClusterNode());
    }

    @Override
    public Properties info() {
        return Optional.ofNullable(redisTemplate.getConnectionFactory().getClusterConnection()).get().info();
    }

    @Override
    public Long hDelete(Object key, Object... hashKeys) {
        return redisTemplate.opsForHash().delete(key, hashKeys);
    }

    @Override
    public Boolean hHasKey(Object key, Object hashKey) {
        return redisTemplate.opsForHash().hasKey(key, hashKey);
    }

    @Override
    public Object hGet(Object key, Object hashKey) {
        return redisTemplate.opsForHash().get(key, hashKey);
    }

    @Override
    public List<Object> hMultiGet(Object key, Collection<Object> hashKeys) {
        return redisTemplate.opsForHash().multiGet(key, hashKeys);
    }

    @Override
    public Long hIncrement(Object key, Object hashKey, long delta) {
        return redisTemplate.opsForHash().increment(key, hashKey, delta);
    }

    @Override
    public Long hLengthOfValue(Object key, Object hashKey) {
        return redisTemplate.opsForHash().lengthOfValue(key, hashKey);
    }

    @Override
    public Long hSize(Object key) {
        return redisTemplate.opsForHash().size(key);
    }

    @Override
    public void hPut(Object key, Map<?, ?> m) {
        redisTemplate.opsForHash().putAll(key, m);
    }

    @Override
    public void hPut(Object key, Object hashKey, Object value) {
        redisTemplate.opsForHash().put(key, hashKey, value);
    }

    @Override
    public Boolean hPutIfAbsent(Object key, Object hashKey, Object value) {
        return redisTemplate.opsForHash().putIfAbsent(key, hashKey, value);
    }

    @Override
    public Map<Object, Object> hGet(Object key) {
        return redisTemplate.opsForHash().entries(key);
    }

    @Override
    public Cursor<Map.Entry<Object, Object>> hScan(Object key, ScanOptions options) {
        return redisTemplate.opsForHash().scan(key, options);
    }
}
